﻿using System;
using System.Reflection;

namespace WCSFContrib.Extensions.EventBroker
{
	/// <summary>
	/// A subscriber container for an instance of a type and one of it's subscriptions
	/// </summary>
	public class Subscriber : EventBrokingData<MethodInfo>
	{
		private MethodType methodCallType;

		/// <summary>
		/// Creates a subscriber from an instance and the data.
		/// </summary>
		/// <param name="instance">The subscribing instance</param>
		/// <param name="topic">The topic subscribed</param>
		/// <param name="methodInfo">The method to call on subscription</param>
		public Subscriber(object instance, string topic, MethodInfo methodInfo)
			: this(instance, new MemberReference<MethodInfo>(topic, methodInfo))
		{}

		/// <summary>
		/// Creates a subscriber from an instance and the data.
		/// </summary>
		/// <param name="instance">The subscribing instance</param>
		/// <param name="subscriptionDetails">Member info and topic data structure.</param>
		public Subscriber(object instance, MemberReference<MethodInfo> subscriptionDetails)
			: base(instance, subscriptionDetails)
		{
			if (memberTopicData.Info.GetParameters().Length == 0)
				methodCallType = MethodType.Parameterless;
			else if (EventUtils.HasEventHandlerSignature(memberTopicData.Info))
				methodCallType = MethodType.ForwardEvent;
			else
				methodCallType = MethodType.ParametersFromGenericEventArg;
		}

		/// <summary>
		/// Create a delegate to listen to the event type.
		/// </summary>
		/// <param name="eventType">The event to listen to.</param>
		/// <returns>A delegate which listens to the eventType.</returns>
		/// <exception cref="WCSFContrib.Extensions.CompositeWeb.ObjectBuilder.EventBroker.EventHookupException">Thrown if the delegate can not be created.</exception>
		public Delegate GetDelegate(Type eventType)
		{
			MethodInfo delegateMethod = null;
			object target = null;
			switch (methodCallType)
			{
				case MethodType.Parameterless:
					delegateMethod = GetType().GetMethod("Handler", BindingFlags.Instance | BindingFlags.NonPublic);
					target = this;
					break;
				case MethodType.ParametersFromGenericEventArg:
					delegateMethod = GetType().GetMethod("GenericHandler", BindingFlags.Instance | BindingFlags.NonPublic);
					target = this;
					break;
				case MethodType.ForwardEvent:
					delegateMethod = memberTopicData.Info;
					target = instanceRef.Target;
					break;
			}

			// Target may have died...
			try
			{
				return IsAlive ?
				       Delegate.CreateDelegate(eventType, target, delegateMethod) :
						 null;
			}
			catch (ArgumentException ae)
			{
				// Arguments on the delegate method does not match the eventType!
				throw new EventHookupException(instanceRef.Target.GetType().FullName, MemberInfo.Name, eventType.FullName, ae);
			}
		}

		#region private
		/// <summary>
		/// Use this handler when the subscriber is parameterless:
		/// Method();
		/// </summary>
		/// <param name="sender">The instance which wants the call</param>
		/// <param name="e">The method to call on subsrciber.</param>
		private void Handler(object sender, EventArgs e)
		{
			object instance = Instance;
			if (instance == null)
				return;
			memberTopicData.Info.Invoke(Instance, null);
		}

		/// <summary>
		/// Use this handler when the subscriber has one parameter which is the same as the generic data
		/// Method(T t);
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void GenericHandler(object sender, EventArgs e)
		{
			object instance = Instance;
			if (instance == null)
				return;

			PropertyInfo[] properties = e.GetType().GetProperties();
			object[] parameters = new object[properties.Length];
			for (int i = 0; i < properties.Length; i++)
			{
				parameters[i] = properties[i].GetGetMethod().Invoke(e, null);
			}

			memberTopicData.Info.Invoke(instance, parameters);
		}

		/// <summary>
		/// The three cases of method handlers in existance.
		/// The expectancy is that Subscriber will be used to create delegates which match the subscribing signature.
		/// </summary>
		private enum MethodType
		{
			/// <summary>
			/// No parameters on the subscribing handler.
			/// </summary>
			Parameterless = 0,
			/// <summary>
			/// Subscribing handler which expects data extracted from the EventArgs.
			/// </summary>
			ParametersFromGenericEventArg,
			/// <summary>
			/// Subscribing handler signature.
			/// </summary>
			ForwardEvent
		}
		#endregion
	}
}